package dbUtils

import (
	sql2 "database/sql"
	"errors"
	"fmt"
	"gitee.com/lv_baobao/gcore/helper"
	"gitee.com/lv_baobao/gcore/logHelper"
	"github.com/go-xorm/xorm"
	"strconv"
	"strings"
	"xorm.io/core"
)

type MySqlQueryable struct {
	IQueryable
	TableName   string
	Prefix      string
	Dbkey       string
	WhereMap    map[string]interface{}
	OrderStr    strings.Builder
	takeSize    int32
	SetMap      map[string]interface{}
	dao         *BaseDao
	QueryFields string
}

func NewMySqlQueryable() IQueryable {
	var qy IQueryable
	var query = MySqlQueryable{}
	query.WhereMap = make(map[string]interface{})
	query.OrderStr = strings.Builder{}
	qy = &query
	return qy
}

func (queryable *MySqlQueryable) SelectFields(fields string) IQueryable {
	queryable.QueryFields = fields
	return queryable.IQueryable
}

func (queryable *MySqlQueryable) GetWhereMap() map[string]interface{} {
	return queryable.WhereMap
}

func (queryable *MySqlQueryable) getWhereKeysAndArgsAndWhereSql() ([]string, []interface{}, string) {
	var whereArgs = make([]interface{}, 0)
	var whereKeys = make([]string, 0)
	if len(queryable.WhereMap) > 0 {
		for k := range queryable.WhereMap {
			whereKeys = append(whereKeys, k)
		}
	}
	for _, key := range whereKeys {
		elems := queryable.WhereMap[key]
		if elems != nil {
			if ids, ok := elems.([]interface{}); ok {
				for _, id := range ids {
					whereArgs = append(whereArgs, id)
				}
			} else if strArr, ok := elems.([]string); ok {
				for _, id := range strArr {
					whereArgs = append(whereArgs, id)
				}
			} else {
				whereArgs = append(whereArgs, elems)
			}
		}
	}
	whereSql := strings.Builder{}
	for index, key := range whereKeys {
		if index == 0 {
			whereSql.WriteString(fmt.Sprintf(" where 1=1 %s ", key))
		} else {
			whereSql.WriteString(fmt.Sprintf(" %s ", key))
		}
	}
	return whereKeys, whereArgs, whereSql.String()
}

func (dao *MySqlQueryable) getDbHelper() *xorm.Engine {
	var db *xorm.Engine
	if dao.Dbkey == "" {
		db = NewDbHelper().GetWriteDb()
	} else {
		db = NewDbHelper().GetDbByKey(dao.Dbkey)
	}

	if dao.Prefix != "" {
		tbMapper := core.NewPrefixMapper(core.SameMapper{}, dao.Prefix)
		db.SetTableMapper(tbMapper)
	}
	return db
}

func (dao *MySqlQueryable) From(tableName string) IQueryable {
	dao.TableName = tableName
	return dao.IQueryable
}

func (dao *MySqlQueryable) Take(size int32) IQueryable {
	dao.takeSize = size
	return dao.IQueryable
}

func (dao *MySqlQueryable) WhereIf(condition string, arg interface{}, condtion bool) IQueryable {
	if condtion {
		dao.WhereMap[condition] = arg
	}
	return dao.IQueryable
}

func (dao *MySqlQueryable) Where(condition string, arg interface{}) IQueryable {
	return dao.WhereIf(condition, arg, true)
}

func (queryable *MySqlQueryable) WhereIn(fieldName string, args []interface{}, condition bool) IQueryable {
	if !condition {
		return queryable
	}
	builder := strings.Builder{}
	builder.WriteString(fmt.Sprintf(" and %s in(", fieldName))
	for i, _ := range args {
		if i == 0 {
			builder.WriteString("?")
		} else {
			builder.WriteString(",?")
		}
	}
	builder.WriteString(")")
	return queryable.WhereIf(builder.String(), args, true)
	//return queryable.WhereIf(" and "+fieldName+" in "+builder.String(), nil, true)
}

func (queryable *MySqlQueryable) In(fieldName string, args ...interface{}) IQueryable {
	builder := strings.Builder{}
	builder.WriteString(fmt.Sprintf(" and %s in(", fieldName))
	inArgs := make([]interface{}, 0)
	for i, _ := range args {
		if i == 0 {
			builder.WriteString("?")
		} else {
			builder.WriteString(",?")
		}
	}
	builder.WriteString(")")
	inArgs = append(inArgs, args...)
	return queryable.WhereIf(builder.String(), inArgs, true)
}

func (dao *MySqlQueryable) Order(orderField string) IQueryable {
	if dao.OrderStr.Len() == 0 {
		dao.OrderStr.WriteString(" order by " + orderField + " asc ")
	} else {
		dao.OrderStr.WriteString(" , " + orderField + " asc ")
	}
	return dao.IQueryable
}

func (dao *MySqlQueryable) OrderByDesc(orderField string) IQueryable {
	if dao.OrderStr.Len() == 0 {
		dao.OrderStr.WriteString(" order by " + orderField + " desc ")
	} else {
		dao.OrderStr.WriteString(" , " + orderField + " desc ")
	}
	return dao.IQueryable
}

func (queryable *MySqlQueryable) Page(pageIndex int, pageSize int, beansPtr interface{}) *helper.QueryResult {
	if pageIndex <= 0 {
		panic(errors.New(fmt.Sprintf("pageIndex is %d", pageIndex)))
	}
	var res = new(helper.QueryResult)
	defer queryable.dao.Close()
	builder := strings.Builder{}
	countBuilder := strings.Builder{}
	countBuilder.WriteString(fmt.Sprintf("select count(0) as tableRowsCount from %s   ", queryable.TableName))
	if queryable.QueryFields != "" {
		builder.WriteString(fmt.Sprintf("select %s from %s  ", queryable.QueryFields, queryable.TableName))
	} else {
		builder.WriteString(fmt.Sprintf("select * from %s  ", queryable.TableName))
	}

	_, whereArgs, whereSql := queryable.getWhereKeysAndArgsAndWhereSql()

	builder.WriteString(whereSql)
	builder.WriteString(queryable.OrderStr.String())
	builder.WriteString(fmt.Sprintf(" limit %d,%d", (pageIndex-1)*pageSize, pageSize))

	countBuilder.WriteString(whereSql)

	var err error
	if queryable.dao.useTransaction {
		err = queryable.dao.getDbSession().SQL(builder.String(), whereArgs...).Find(beansPtr)
	} else {
		err = queryable.dao.GetDbHelper().SQL(builder.String(), whereArgs...).Find(beansPtr)
	}
	if err != nil {
		logHelper.Error(err)
	}

	var sqlOrArgs = make([]interface{}, 0)
	sqlOrArgs = append(sqlOrArgs, countBuilder.String())
	sqlOrArgs = append(sqlOrArgs, whereArgs...)

	var result []map[string][]byte
	if queryable.dao.useTransaction {
		result, _ = queryable.dao.getDbSession().Query(sqlOrArgs...)
	} else {
		result, _ = queryable.dao.GetDbHelper().Query(sqlOrArgs...)
	}
	if err != nil {
		logHelper.Error(err)
		return res
	}
	if result != nil {
		res.TotalRecords, _ = strconv.ParseInt(string(result[0]["tableRowsCount"]), 10, 32)
	}
	res.PageSize = pageSize
	res.PageIndex = pageIndex
	if res.TotalRecords%int64(pageSize) == 0 {
		res.TotalPage = res.TotalRecords / int64(pageSize)
	} else {
		res.TotalPage = res.TotalRecords/int64(pageSize) + 1
	}
	res.PageCount = res.TotalPage
	res.ResultList = beansPtr
	queryable.WhereMap = nil
	return res
}

func (queryable *MySqlQueryable) List(beansPtr interface{}) {
	defer queryable.dao.Close()
	builder := strings.Builder{}
	if queryable.QueryFields != "" {
		builder.WriteString(fmt.Sprintf("select %s from %s   ", queryable.QueryFields, queryable.TableName))
	} else {
		builder.WriteString(fmt.Sprintf("select * from %s   ", queryable.TableName))
	}

	_, whereArgs, whereSql := queryable.getWhereKeysAndArgsAndWhereSql()
	builder.WriteString(whereSql)
	builder.WriteString(queryable.OrderStr.String())
	if queryable.takeSize > 0 {
		builder.WriteString(fmt.Sprintf(" limit %d ", queryable.takeSize))
	}
	var err error
	err = queryable.dao.getDbSession().SQL(builder.String(), whereArgs...).Find(beansPtr)
	if err != nil {
		logHelper.Error(err)
	}
}

func (queryable *MySqlQueryable) FirstOrDefault(beanPtr interface{}) {
	defer queryable.dao.Close()
	builder := strings.Builder{}
	if queryable.QueryFields != "" {
		builder.WriteString(fmt.Sprintf("select %s from %s   ", queryable.QueryFields, queryable.TableName))
	} else {
		builder.WriteString(fmt.Sprintf("select * from %s   ", queryable.TableName))
	}
	_, whereArgs, whereSql := queryable.getWhereKeysAndArgsAndWhereSql()
	if whereSql != "" {
		builder.WriteString(whereSql)
	}
	if queryable.OrderStr.Len() > 0 {
		builder.WriteString(queryable.OrderStr.String())
	}
	builder.WriteString(" limit 1 ")
	var err error
	_, err = queryable.dao.getDbSession().SQL(builder.String(), whereArgs...).Get(beanPtr)
	if err != nil {
		logHelper.Error(err)
	}
	if err != nil {
		logHelper.Error(err)
	}
}

func (queryable *MySqlQueryable) Delete() int64 {
	defer queryable.dao.Close()
	builder := strings.Builder{}
	builder.WriteString(fmt.Sprintf("delete from %s ", queryable.TableName))

	whereKeys, whereArgs, whereSql := queryable.getWhereKeysAndArgsAndWhereSql()
	if whereSql != "" {
		builder.WriteString(whereSql)
	}
	var sqlOrArgs = make([]interface{}, 0)
	sqlOrArgs = append(sqlOrArgs, builder.String())
	if len(whereArgs) > 0 {
		sqlOrArgs = append(sqlOrArgs, whereArgs...)
	}
	var exec sql2.Result
	var err error
	exec, err = queryable.dao.getDbSession().Exec(sqlOrArgs...)
	if err != nil {
		logHelper.Error(err)
		return 0
	}
	rows, err := exec.RowsAffected()
	if err != nil {
		logHelper.Error(err)
		return 0
	}
	if queryable.dao.EnableSyncDb() {
		dbConfig := NewDbHelper().GetDbConfig(queryable.Dbkey)
		NewDbLogHelper().WriteDeletedLog(queryable.TableName, whereKeys, whereArgs, dbConfig.DataBase, queryable.Dbkey)
	}
	return rows
}

func (queryable *MySqlQueryable) Count() int64 {
	defer queryable.dao.Close()
	countBuilder := strings.Builder{}
	countBuilder.WriteString(fmt.Sprintf("select count(0) as tableRowsCount from %s ", queryable.TableName))

	_, whereArgs, whereSql := queryable.getWhereKeysAndArgsAndWhereSql()
	if whereSql != "" {
		countBuilder.WriteString(whereSql)
	}
	var sqlOrArgs = make([]interface{}, 0)
	sqlOrArgs = append(sqlOrArgs, countBuilder.String())
	if len(whereArgs) > 0 {
		sqlOrArgs = append(sqlOrArgs, whereArgs...)
	}
	var err error
	var result []map[string][]byte
	result, err = queryable.dao.getDbSession().Query(sqlOrArgs...)
	if err != nil {
		logHelper.Error(err)
	}
	var totalRecords int64 = 0
	if result != nil {
		totalRecords, _ = strconv.ParseInt(string(result[0]["tableRowsCount"]), 10, 32)
	}
	queryable.WhereMap = nil
	return totalRecords
}

func (queryable *MySqlQueryable) Set(field string, arg interface{}) IQueryable {
	if queryable.SetMap == nil {
		queryable.SetMap = make(map[string]interface{})
	}
	queryable.SetMap[field] = arg
	return queryable.IQueryable
}

func (queryable *MySqlQueryable) Update() bool {
	if queryable.SetMap == nil {
		return false
	}
	defer queryable.dao.Close()
	builder := strings.Builder{}
	builder.WriteString(fmt.Sprintf("update %s ", queryable.TableName))

	var setArgs = make([]interface{}, 0)
	var setKeys = make([]string, 0)
	if len(queryable.SetMap) > 0 {
		for k := range queryable.SetMap {
			setKeys = append(setKeys, k)
		}
	}

	for index, key := range setKeys {
		if index == 0 {
			builder.WriteString(fmt.Sprintf("set %s=?", key))
		} else {
			builder.WriteString(fmt.Sprintf(",%s=?", key))
		}
	}

	for _, key := range setKeys {
		if queryable.SetMap[key] != nil {
			setArgs = append(setArgs, queryable.SetMap[key])
		}
	}

	whereKeys, whereArgs, whereSql := queryable.getWhereKeysAndArgsAndWhereSql()
	if whereSql != "" {
		builder.WriteString(whereSql)
	}
	sql := builder.String()
	var sqlOrArgs = make([]interface{}, 0)
	sqlOrArgs = append(sqlOrArgs, sql)
	if len(setArgs) > 0 {
		sqlOrArgs = append(sqlOrArgs, setArgs...)
	}
	if len(whereArgs) > 0 {
		sqlOrArgs = append(sqlOrArgs, whereArgs...)
	}
	var exec sql2.Result
	var err error
	exec, err = queryable.dao.getDbSession().Exec(sqlOrArgs...)
	if err != nil {
		logHelper.Error(err)
		return false
	}
	var rows, _ = exec.RowsAffected()
	if rows > 0 {
		if queryable.dao.EnableSyncDb() {
			dbConfig := NewDbHelper().GetDbConfig(queryable.Dbkey)
			NewDbLogHelper().WriteUpdatedLog(queryable.TableName, setKeys, setArgs, whereKeys, whereArgs, dbConfig.DataBase, queryable.Dbkey)
		}
	}
	return rows > 0
}
